#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <string.h>
#include "LinkedList.h"
#include <ctype.h>

char* toLower(char* str);

typedef struct StrtokRes {
    char* str;
    char* tail;
} StrtokRes;

StrtokRes tokenize(char * str, const char * delim);
#define INPUT_BUFFER_SIZE 1024
#define PROC_SIZE INPUT_BUFFER_SIZE*32

typedef enum ReadMode {
    NORMAL = 0,
    IGNORE = 1,
    PROCEDURE = 2
} ReadMode;

char* doUntil(StrtokRes tok, char* endStrArr[], ReadMode readMode);

typedef enum OpType {
    Integer = 0,
    String = 1,
    Constant = 2,
    Variable = 3,
    Operator = 4,
    Boolean = 5,
    Procedure = 6,
    NullOp = 7
} OpType;

typedef struct Inst {
    OpType type;
    char *name;
    void *value;
} Inst;

Inst* dataStack[1024 * 1024];
char* returnStack[1024 * 1024];
int stackPointer;
int returnPointer;
LinkedList *dict;
int ifCount = 0;

typedef void (*opFunc)(void);
typedef int (*CompareFunc)(int, int);

int isBoolean(char* str) {
    int res = 0;
    if (strcmp(toLower(str), "true") == 0 || strcmp(toLower(str), "false") == 0) {
        res = 1;
    }
    return res;
}

int isNumber(char *str) {
    int i = 0;
    int res = 1;
    int isNegative = *str == '-' && strlen(str) > 1;
    if (isNegative) {
        i = 1;
    }
    while (*(str + i) != 0) {
        if (*(str + i) > '9' || *(str + i) < '0') {
            res = 0;
            break;
        }
        i++;
    }
    return res;
}

int isString(char *str) {
    int res = 0;
    if (*str == '\"' && *(str + strlen(str) - 1) == '\"') {
        res = 1;
    }
    return res;
}

Inst* createInst(OpType type, char *name, void *value) {
    Inst *res = (Inst *) malloc(sizeof (Inst));
    if (res == NULL) {
        exit(-1);
    }
    res->type = type;
    res->name = name;
    res->value = value;
    return res;
}

Inst* pop(void) {
    Inst *res;
    if (stackPointer - 1 < 0) {
        return NULL;
    }
    res = dataStack[stackPointer];
    stackPointer--;
    return res;
}

void push(Inst* inst) {
    stackPointer++;
    dataStack[stackPointer] = inst;
}

void pushReturnStack(char* str) {
    returnPointer++;
    returnStack[returnPointer] = str;
}

char* popReturnStack() {
    char *res;
    if (returnPointer - 1 < 0) {
        return NULL;
    }
    res = returnStack[returnPointer];
    returnPointer--;
    return res;
}

//Arithmatic functions

void add(void) {
    Inst *op1 = pop();
    Inst *op2 = pop();
    if (op1 == NULL || op2 == NULL) {
        printf("Not enough operands!!!\n");
        return;
    }
    if (op1->type != Integer || op2->type != Integer) {
        printf("Wrong operand(s) type!!!\n");
        return;
    }
    push(createInst(Integer, "", (void*) (((int) op2->value)+((int) op1->value))));
}

void sub(void) {
    Inst *op1 = pop();
    Inst *op2 = pop();
    if (op1 == NULL || op2 == NULL) {
        printf("Not enough operands!!!\n");
        return;
    }
    if (op1->type != Integer || op2->type != Integer) {
        printf("Wrong operand(s) type!!!\n");
        return;
    }
    push(createInst(Integer, "", (void*) (((int) op2->value)-((int) op1->value))));
}

void mul(void) {
    Inst *op1 = pop();
    Inst *op2 = pop();
    if (op1 == NULL || op2 == NULL) {
        printf("Not enough operands!!!\n");
        return;
    }
    if (op1->type != Integer || op2->type != Integer) {
        printf("Wrong operand(s) type!!!\n");
        return;
    }
    push(createInst(Integer, "", (void*) (((int) op2->value)*((int) op1->value))));
}

void div1(void) {
    Inst *op1 = pop();
    Inst *op2 = pop();
    if (op1 == NULL || op2 == NULL) {
        printf("Not enough operands!!!\n");
        return;
    }
    if (op1->type != Integer || op2->type != Integer) {
        printf("Wrong operand(s) type!!!\n");
        return;
    }
    push(createInst(Integer, "", (void*) (((int) op2->value) / ((int) op1->value))));
}

void mod(void) {
    Inst *op1 = pop();
    Inst *op2 = pop();
    if (op1 == NULL || op2 == NULL) {
        printf("Not enough operands!!!\n");
        return;
    }
    if (op1->type != Integer || op2->type != Integer) {
        printf("Wrong operand(s) type!!!\n");
        return;
    }
    push(createInst(Integer, "", (void*) (((int) op2->value) % ((int) op1->value))));
}

void abs1(void) {
    Inst *op1 = pop();
    if (op1 == NULL) {
        printf("Not enough operands!!!\n");
        return;
    }
    if (op1->type != Integer) {
        printf("Wrong operand(s) type!!!\n");
        return;
    }
    push(createInst(Integer, "", (void*) abs(((int) op1->value))));
}

//System functions

void displayAux(Inst * toDisplay) {
    switch (toDisplay->type) {
        case Integer:
            printf("Integer Name: %s Value: %d\n", (char*) toDisplay->name, (int) toDisplay->value);
            break;
        case String:
            printf("String Name: %s Value: %s\n", (char*) toDisplay->name, (char*) toDisplay->value);
            break;
        case Constant:
            printf("Constant Name: %s Value: %d\n", (char*) toDisplay->name, (int) toDisplay->value);
            break;
        case Boolean:
            printf("Boolean Name: %s Value: %s\n", (char*) toDisplay->name, ((int) toDisplay->value) ? "true" : "false");
            break;
        default:
            printf("Unknown Type Name: %s\n", (char*) toDisplay->name);
            break;
    }
}

void displayRec(void) {
    Inst *toDisplay = pop();
    if (toDisplay == NULL) {
        return;
    }
    displayAux(toDisplay);
    displayRec();
    push(toDisplay);
}

void display(void) {
    Inst *toDisplay = pop();
    if (toDisplay == NULL) {
        printf("Stack Empty!!!\n");
        return;
    }
    displayAux(toDisplay);
    displayRec();
    push(toDisplay);
}

void exitFunc(void) {
    exit(0);
}

void doIfTrue(void) {
    char* endWords[] = {"else", "endif", NULL};
    char* ok[] = {"endif", NULL};
    char* retVal = doUntil(tokenize(NULL, " \n\t\r"), endWords, NORMAL);
    if (strcmp(toLower(retVal), "else") == 0) {
        doUntil(tokenize(NULL, " \n\t\r"), ok, IGNORE);
    }
}

void doIfFalse(void) {
    char* elseWord[] = {"else", "endif", NULL};
    char* ok[] = {"endif", NULL};
    char* retVal = doUntil(tokenize(NULL, " \n\t\r"), elseWord, IGNORE);
    if (strcmp(toLower(retVal), "else") == 0) {
        doUntil(tokenize(NULL, " \n\t\r"), ok, NORMAL);
    }
}

void ifFunc(void) {
    Inst* cond = pop();
    if (cond == NULL) {
        printf("Not enough operands!!!\n");
        return;
    }
    if (cond->type != Boolean) {
        printf("Wrong operand(s) type!!!\n");
        return;
    }
    if ((int) cond->value) {
        doIfTrue();
    } else {
        doIfFalse();
    }
}

int stringCompare(void* s1, void* s2) {
    return strcmp(s1, s2);
}

void makeProcedure(void) {
    Inst* name = pop();
    if (name == NULL) {
        printf("Not enough operands!!!\n");
        return;
    }
    if (name->type != String) {
        printf("Wrong operand(s) type!!!\n");
        return;
    }
    char* endWords[] = {"endproc", NULL};
    char* readLoopRes = "";
    char* procStr = (char*) malloc(sizeof (char) *PROC_SIZE);
    if (!procStr) {
        exit(-1);
    }
    procStr[0] = '\0';
    int first = 1;
    while (readLoopRes != NULL && !contains(endWords, toLower(readLoopRes))) {
        readLoopRes = doUntil(tokenize(NULL, " \n\t\r"), endWords, PROCEDURE);
        if (readLoopRes != NULL && !contains(endWords, toLower(readLoopRes))) {
            if (!first) {
                strcat(procStr, " ");
            } else {
                first = 0;
            }
            strcat(procStr, readLoopRes);
        }
    }
    LLAdd(dict, createInst(Procedure, (char*) name->value, (void*) procStr));
}

// Boolean functions

void notFunc(void) { // ! op1
    Inst* cond = pop();
    if (cond == NULL) {
        printf("Not enough operands!!!\n");
        return;
    }
    if (cond->type != Boolean) {
        printf("Wrong operand(s) type!!!\n");
        return;
    }
    push(createInst(Boolean, "", (void*) (((int) cond->value) ? 0 : 1)));
}

int compare(Inst *op1, Inst *op2, CompareFunc comp) {
    int res;
    switch (op1->type) {
        case String:
            res = comp(strcmp((char*) op2->value, (char*) op1->value), 0);
            break;
        case Constant:
            res = compare((Inst*) op1->value, (Inst*) op2->value, comp);
            break;
        default:
            res = comp((int) op2->value, (int) op1->value);
            break;
    }
    return res;
}

int equalsCompare(int v1, int v2) {
    return v1 == v2 ? 1 : 0;
}

int notEqualsCompare(int v1, int v2) {
    return v1 != v2 ? 1 : 0;
}

void isEqual(void) { // op1 == op2
    Inst *op1 = pop();
    Inst *op2 = pop();
    if (op1 == NULL || op2 == NULL) {
        printf("Not enough operands!!!\n");
        return;
    }
    if (op1->type != op2->type) {
        push(createInst(Boolean, "", (void*) 0));
        return;
    }
    push(createInst(Boolean, "", (void*) compare(op1, op2, equalsCompare)));
}

void isNotEqual(void) { // op1 == op2
    Inst *op1 = pop();
    Inst *op2 = pop();
    if (op1 == NULL || op2 == NULL) {
        printf("Not enough operands!!!\n");
        return;
    }
    if (op1->type != op2->type) {
        push(createInst(Boolean, "", (void*) 1));
        return;
    }
    push(createInst(Boolean, "", (void*) compare(op1, op2, notEqualsCompare)));
}

int greaterCompare(int v1, int v2) {
    return v1 > v2 ? 1 : 0;
}

void isGreater(void) { // op1 > op2
    Inst *op1 = pop();
    Inst *op2 = pop();
    if (op1 == NULL || op2 == NULL) {
        printf("Not enough operands!!!\n");
        return;
    }
    if (op1->type != op2->type) {
        push(createInst(Boolean, "", (void*) 0));
        return;
    }
    push(createInst(Boolean, "", (void*) compare(op1, op2, greaterCompare)));
}

int notSmallerCompare(int v1, int v2) {
    return v1 >= v2 ? 1 : 0;
}

void isNotSmaller(void) {// op1 >= op2
    Inst *op1 = pop();
    Inst *op2 = pop();
    if (op1 == NULL || op2 == NULL) {
        printf("Not enough operands!!!\n");
        return;
    }
    if (op1->type != op2->type) {
        push(createInst(Boolean, "", (void*) 0));
        return;
    }
    push(createInst(Boolean, "", (void*) compare(op1, op2, notSmallerCompare)));
}

int smallerCompare(int v1, int v2) {
    return v1 < v2 ? 1 : 0;
}

void isSmaller(void) { // op1 < op2
    Inst *op1 = pop();
    Inst *op2 = pop();
    if (op1 == NULL || op2 == NULL) {
        printf("Not enough operands!!!\n");
        return;
    }
    if (op1->type != op2->type) {
        push(createInst(Boolean, "", (void*) 0));
        return;
    }
    push(createInst(Boolean, "", (void*) compare(op1, op2, smallerCompare)));
}

int notGreaterCompare(int v1, int v2) {
    return v1 <= v2 ? 1 : 0;
}

void isNotGreater(void) {// op1 <= op2
    Inst *op1 = pop();
    Inst *op2 = pop();
    if (op1 == NULL || op2 == NULL) {
        printf("Not enough operands!!!\n");
        return;
    }
    if (op1->type != op2->type) {
        push(createInst(Boolean, "", (void*) 0));
        return;
    }
    push(createInst(Boolean, "", (void*) compare(op1, op2, notGreaterCompare)));
}

//Stack functions

void drop(void) {
    pop();
}

void dup1(void) {
    Inst *toDup = pop();
    if (toDup == NULL) {
        printf("Not enough operands!!!\n");
        return;
    }
    push(toDup);
    push(toDup);
}

void dup2(void) {
    Inst *toDup = pop();
    if (toDup == NULL) {
        printf("Not enough operands!!!\n");
        return;
    }
    push(toDup);
    push(toDup);
    push(toDup);
}

void swap(void) {
    Inst *s1 = pop();
    Inst *s2 = pop();
    if (s1 == NULL || s2 == NULL) {
        printf("Not enough operands!!!\n");
        return;
    }
    push(s1);
    push(s2);
}

void over(void) {
    Inst *tmp = pop();
    if (tmp == NULL) {
        printf("Not enough operands!!!\n");
        return;
    }
    Inst *toDup = pop();
    if (toDup == NULL) {
        printf("Not enough operands!!!\n");
        push(tmp);
        return;
    }
    push(toDup);
    push(tmp);
    push(toDup);
}

void rrot(void) {
    Inst *a = pop();
    Inst *b = pop();
    Inst *c = pop();
    if (a == NULL || b == NULL || c == NULL) {
        printf("Not enough operands!!!\n");
        return;
    }
    push(b);
    push(a);
    push(c);
}

void lrot(void) {
    Inst *a = pop();
    Inst *b = pop();
    Inst *c = pop();
    if (a == NULL || b == NULL || c == NULL) {
        printf("Not enough operands!!!\n");
        return;
    }
    push(a);
    push(c);
    push(b);
}

void over2(void) {
    Inst *a = pop();
    Inst *b = pop();
    Inst *c = pop();
    if (a == NULL || b == NULL || c == NULL) {
        printf("Not enough operands!!!\n");
        return;
    }
    push(c);
    push(b);
    push(a);
    push(c);
}

void swap2(void) {
    Inst *a = pop();
    Inst *b = pop();
    Inst *c = pop();
    if (a == NULL || b == NULL || c == NULL) {
        printf("Not enough operands!!!\n");
        return;
    }
    push(a);
    push(b);
    push(c);
}


//Constants

void constant(void) {
    Inst *name = pop();
    Inst *val = pop();
    if (name->type != String) {
        printf("Second argument (name) must be a string\n");
        return;
    }
    Inst *constant = createInst(Constant, name->value, val);
    LLNode *node = LLFind(dict, constant);
    if (node != NULL) {
        ((Inst*) node->data)->value = val;
    } else {
        LLAdd(dict, constant);
    }
}

StrtokRes tokenize(char * str, const char * delim) {
    static char* p = 0;
    StrtokRes res;
    res.str = NULL;
    res.tail = NULL;
    if (str) {
        p = str;
    }
    if (p) {
        res.str = p + strspn(p, delim);
        p = res.str + strcspn(res.str, delim);
        if (p == res.str) {
            res.str = p = 0;
        } else {
            res.tail = p = *p ? *p = 0, p + 1 : 0;
        }
    }
    return res;
}

char* strDup(char* str) {
    int srcLength = strlen(str);
    char* res = (char*) malloc(sizeof (char) *(srcLength + 1));
    if (res == NULL) {
        exit(-1);
    }
    strcpy(res, str);
    return res;
}

char* strIgnrTok(char* str) {
    int srcLength = strlen(str) + 2;
    char* res = (char*) malloc(sizeof (char) *(srcLength + 1));
    if (res == NULL) {
        exit(-1);
    }
    res[0] = '#';
    res[1] = ' ';
    strcat(res, str);
    return res;
}

char* toLower(char* str) {
    int i;
    char* res = strDup(str);
    for (i = 0; i < strlen(res); ++i) {
        res[i] = tolower(res[i]);
    }
    return res;
}

char* removeQuotes(char* str) {
    int i;
    int srcLength = strlen(str);
    char* res = (char*) malloc(sizeof (char) *(srcLength - 1));
    if (res == NULL) {
        exit(-1);
    }
    for (i = 1; i < srcLength - 1; ++i) {
        res[i - 1] = str[i];
    }
    res[i] = '\0';
    return res;
}

int contains(char* arr[], char* str) {
    int res = 0;
    int i = 1;
    char* curr = *arr;
    while (!res && curr != NULL) {
        res = res || strcmp(curr, str) == 0;
        curr = *(arr + i);
        i++;
    }
    return res;
}

char* readLoop(StrtokRes tok, char* endStrArr[], ReadMode readMode) {
    while (tok.str != NULL) {
        if (readMode == IGNORE) {
            if (strcmp(tok.str, "if") == 0) {
                ifCount++;
            } else if (strcmp(tok.str, "endif") == 0 && ifCount > 0) {
                ifCount--;
                tok = tokenize(NULL, " \n\t\r");
                continue;
            } else if (strcmp(tok.str, "else") == 0 && ifCount > 0) {
                tok = tokenize(NULL, " \n\t\r");
                continue;
            }
        }
        if (contains(endStrArr, tok.str) || readMode == PROCEDURE) {
            return tok.str;
        } else if (readMode == IGNORE) {
            tok = tokenize(NULL, " \n\t\r");
            continue;
        }
        if (isBoolean(tok.str)) {
            int cond = strcmp(toLower(tok.str), "true") == 0 ? 1 : 0;
            push(createInst(Boolean, "", (void*) cond));
        } else if (isNumber(tok.str)) {
            int num = atoi(tok.str);
            push(createInst(Integer, "", (void*) num));
        } else if (isString(tok.str)) {
            push(createInst(String, "", removeQuotes(tok.str)));
        } else {
            LLNode *node = LLFind(dict, createInst(NullOp, toLower(tok.str), NULL));
            if (node != NULL) {
                Inst* instr = (Inst*) node->data;
                OpType t = instr->type;
                switch (t) {
                    case Operator:
                        ((opFunc) instr->value)();
                        break;
                    case Constant:
                        push(instr);
                        break;
                    case Procedure:
                        do {
                            pushReturnStack(strIgnrTok(tok.tail));
                            char* exitWords[] = {NULL};
                            char procStr[PROC_SIZE];
                            strcpy(procStr, instr->value);
                            StrtokRes procTok = tokenize(procStr, " \n\r\t");
                            readLoop(procTok, exitWords, NORMAL);
                            tok = tokenize(popReturnStack(), " \n\r\t");
                        } while (0);
                        break;
                    default:
                        break;
                }
            } else {
                printf("Invalid Instruction: %s\n", tok.str);
            }
        }
/*
        display();
        printf("--- %s\n", tok.str);
*/
        tok = tokenize(NULL, " \n\t\r");
    }
    return NULL;
}

char* interpreter(char* endStrArr[], ReadMode readMode) {
    char* buf = (char*) malloc(sizeof (char) *INPUT_BUFFER_SIZE);
    char* readLoopRes = NULL;
    while (readLoopRes == NULL || (!contains(endStrArr, toLower(readLoopRes))) && readMode != PROCEDURE) {
        printf("> ");
        size_t bufSize = INPUT_BUFFER_SIZE - 1;
        getline(&buf, &bufSize, stdin);
        StrtokRes tok = tokenize(buf, " \n\r\t");
        readLoopRes = readLoop(tok, endStrArr, readMode);
    }
    return readLoopRes;
}

char* doUntil(StrtokRes tok, char* endStrArr[], ReadMode readMode) {
    char* lastTok = readLoop(tok, endStrArr, readMode);
    if (lastTok == NULL) {
        lastTok = interpreter(endStrArr, readMode);
    }
    return lastTok;
}

int InstComp(void *inst1, void* inst2) {
    Inst *inst11 = (Inst*) inst1;
    Inst *inst22 = (Inst*) inst2;
    return strcmp(inst11->name, inst22->name);
}

void reset() {
    stackPointer = 0;
    returnPointer = 0;
    dict = createLinkedList(&InstComp, NULL, NULL);
    LLAdd(dict, createInst(Operator, "+", (void*) add));
    LLAdd(dict, createInst(Operator, "-", (void*) sub));
    LLAdd(dict, createInst(Operator, "*", (void*) mul));
    LLAdd(dict, createInst(Operator, "/", (void*) div1));
    LLAdd(dict, createInst(Operator, "%", (void*) mod));
    LLAdd(dict, createInst(Operator, "abs", (void*) abs1));
    LLAdd(dict, createInst(Operator, "display", (void*) display));
    LLAdd(dict, createInst(Operator, "exit", (void*) exitFunc));
    LLAdd(dict, createInst(Operator, "drop", (void*) drop));
    LLAdd(dict, createInst(Operator, "dup", (void*) dup1));
    LLAdd(dict, createInst(Operator, "2dup", (void*) dup2));
    LLAdd(dict, createInst(Operator, "swap", (void*) swap));
    LLAdd(dict, createInst(Operator, "over", (void*) over));
    LLAdd(dict, createInst(Operator, "rrot", (void*) rrot));
    LLAdd(dict, createInst(Operator, "lrot", (void*) lrot));
    LLAdd(dict, createInst(Operator, "2over", (void*) over2));
    LLAdd(dict, createInst(Operator, "2swap", (void*) swap2));
    LLAdd(dict, createInst(Operator, "constant", (void*) constant));
    LLAdd(dict, createInst(Operator, "if", (void*) ifFunc));
    LLAdd(dict, createInst(Operator, "!", (void*) notFunc));
    LLAdd(dict, createInst(Operator, "==", (void*) isEqual));
    LLAdd(dict, createInst(Operator, "!=", (void*) isNotEqual));
    LLAdd(dict, createInst(Operator, ">", (void*) isGreater));
    LLAdd(dict, createInst(Operator, ">=", (void*) isNotSmaller));
    LLAdd(dict, createInst(Operator, "<", (void*) isSmaller));
    LLAdd(dict, createInst(Operator, "<=", (void*) isNotGreater));
    LLAdd(dict, createInst(Operator, "proc", (void*) makeProcedure));

}

int main(int argc, char **argv) {
    reset();
    char* exitWords[] = {NULL};
    interpreter(exitWords, NORMAL);
}

/*
 We support:
 *  Integers - 
 *      > 123
 * Strings:
 *      > "Hello inverted hyperbolic tangent"
 * Constants:
 * constant - makes a constant in the environment. defining a constant with an already existing name will overwrite the old value.
 * <val> <name_string> constant
 * > 123 "bobo" constant
 * 
 * Conditional operators + if else endif:
 * true, false, ==, !=, <, >, <=, >=
 * If statement: <bool> if <do_if_true> else <do_if_false> endif
 * > 1 1 == if "hello" else "world" endif
 * 
 * User defined procedures:
 * <name> proc <body> endproc
        -Factorial:
"fact" 
proc 
dup
1
>
if
dup
1
-
fact
 *
endif
endproc

        -Ackermann
"ack"
proc
over
0
==
if
1
+
swap
drop
else
dup
0
==
if
drop
1
-
1
ack
else
over
over
1
-
ack
swap
drop
swap
1
-
swap
ack
endif
endif
endproc
 * 
 * 
 * Library functions:
   + - addition
   - - subtraction
   - multiplication
   / - division
   % - modulu
   abs - absolute value
   display - displays the content of the stack
   exit - terminates the read-eval loop and exits
   drop - removes the top value from the head of the stack
   dup - duplicates the top value from the head of the stack
   2dup - duplicates the top value from the head of the stack twice
   swap - swaps the top value from the head of the stack with the second top value from the head of the stack
   over - duplicates the second top value from the head of the stack
   rrot - rotates the top 3 element of the stack to the right. ABC -> CAB
   lrot - rotates the top 3 element of the stack to the left. ABC -> BCA
   2over - duplicates the third top value from the head of the stack
   2swap - swaps the top value from the head of the stack with the third top value from the head of the stack
 */
